# -*- coding: utf-8 -*-
import re
import time

from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.common.by import By

from base.component import UIConfigLoader, PageNavigator, Locator, Activity, AllPage
from base.devicemanager import DeviceManager
from utils.logoperator import LogOperator

logger = LogOperator(__name__)


class ElementOperator:
    """
    页面、元素操作
    """

    def __init__(self, path=None, page_name=None):
        """
        封装对页面对象、元素对象的操作
        :param path: 页面、元素定位yaml文件
        :param page_name: 页面名
        """
        self.device_manager = DeviceManager()
        self.driver = self.device_manager.driver
        self.adb_operator = self.device_manager.adb_operator
        self.language = self.adb_operator.get_device_language()

        # 加载配置
        self.config_loader = UIConfigLoader(path)
        self.app_name = self.config_loader.app_name
        self.app_activity = f"{self.app_name}.activity"

        # 页面导航
        self.page_name = page_name
        self.navigator = PageNavigator()
        self.navigator.build_page_graph(self.config_loader.pages)

        # 初始化页面元素
        self._page_elements = self._init_page_elements()
        self.ignore_dialog_error = True

    def _init_page_elements(self):
        """
        初始化页面对象和元素对象
        :return: self
        """

        def parse_page(page):
            page_name = page["page"]["pageName"]
            page_desc = page["page"]["desc"]
            origin_locators_list = page["page"]["locators"]
            locators_list = []
            for locator in origin_locators_list:
                if isinstance(locator, list):
                    locators_list.extend(locator)
                else:
                    locators_list.append(locator)

            activity = Activity()
            for locator in locators_list:
                if 'name' in locator:
                    locator_name = locator["name"]
                    by_type, element = self.parse_locator_map(locator)
                    goto_page = locator.get("goto_page", None)
                    desc = f"{page_desc}_{locator['desc']}"
                    dialog = locator.get("dialog", None)
                    locator_obj = Locator(element, by_type, locator_name, goto_page, desc, dialog)
                    setattr(activity, locator_name, locator_obj)
            setattr(allPage, page_name, activity)

        pages_list = self.config_loader.pages
        allPage = AllPage()
        for page_item in pages_list:
            parse_page(page_item)
        setattr(self.__class__, 'allPage', allPage)
        return self

    def __getattr__(self, name):
        if "page_name" in dir(self):
            current_page = self.page_name
            element = self.navigator.page_elements.get(current_page, {}).get(name)
            if element:
                element_proxy = self._create_element_proxy(element)
                return element_proxy
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

    def enter_page(self, target_page):
        """
        进入指定页面，支持自动导航，通过页面找元素

        :param target_page: 目标页面名称
        :return: self: 切换后的 ElementOperator 对象
        """
        try:
            navigation_path = self.navigator.get_navigation_path(self.page_name, target_page)
            if navigation_path:
                logger.info(f"导航路径: {navigation_path}")  # todo, 简化路径打印
            for step in navigation_path:
                element = self._create_element_proxy(step)
                element.click()
                if step.get('dialog'):
                    self._handle_dialog(step.dialog)
            self.page_name = target_page
            return self
        except ValueError as ve:
            logger.error(f"页面路径错误: {ve}")
            raise
        except Exception as e:
            logger.error(f"导航到页面 {target_page} 失败: {e}")
            raise

    def _create_element_proxy(self, item_dict):
        """创建元素对象"""
        if 'name' in item_dict:
            locator_name = item_dict["name"]
            by_type, element = self.parse_locator_map(item_dict)
            goto_page = item_dict.get("goto_page", None)
            desc = f"{item_dict['desc']}"
            dialog = item_dict.get("dialog", None)
            locator_obj = Locator(element, by_type, locator_name, goto_page, desc, dialog)
            return ElementProxy(self, locator_obj)

    def click(self, *args, **kwargs):
        if kwargs != {}:  # 一个要点击的元素，且有弹框
            _locator = getattr(self, args[0]) if isinstance(args[0], str) else args[0]
            _dialog = kwargs.get('dialog')
            expect_goto_page = self.click_locator(_locator)
            current_page = self.click_dialog(_dialog)
            if expect_goto_page == current_page:
                self.page_name = expect_goto_page
            else:
                self.page_name = current_page
                logger.error(f"预期在未点击前的页面「{self.page_name}」，实际页面「{current_page}」")
        else:  # 一个或者多个元素且无弹框/多个元素（含处理弹框)构成的元组
            for item in args:
                if isinstance(item, tuple):
                    _locator, _dialog = item
                    expected_goto_page = self.click_locator(_locator)
                    current_page = self.click_dialog(_dialog)
                    if expected_goto_page == current_page:
                        self.page_name = expected_goto_page
                    else:
                        if current_page is not None and current_page not in self.dialog_activity():
                            self.page_name = current_page
                else:
                    expected_goto_page = self.click_locator(item)
                    current_page = self.get_current_page_activity()
                    if expected_goto_page == current_page:
                        self.page_name = expected_goto_page
                    else:
                        if current_page is not None and current_page not in self.dialog_activity():
                            self.page_name = current_page

    def _handle_dialog(self, dialog):
        if isinstance(dialog, dict):
            dialog_type = dialog.get('type', 'xpath')
            dialog_values = dialog.get('value', [])
            if isinstance(dialog_values, list):
                if self.language in ['zh_CN', 'zh']:
                    dialog_value = dialog_values[0]
                else:
                    dialog_value = dialog_values[1]
            else:
                dialog_value = dialog_values
            try:
                self.driver.find_element_by_xpath(dialog_value).click()
            except Exception as e:
                logger.warning(f"处理对话框失败: {e}")

    def open_app(self, app_name):
        """
        打开应用
        """
        self.adb_operator.open_app(app_name)

    @staticmethod
    def _get_locator_name_value(locator):
        type_dict = {
            "xpath": By.XPATH,
            "id": By.ID,
        }
        name, value = locator.by_type, locator.element
        if isinstance(name, str):
            locator_name_value = (type_dict[locator.by_type], locator.element)
        else:
            locator_name_value = dict(zip(name, value))
        return locator_name_value

    def parse_locator_map(self, locator):
        xpath = By.XPATH
        by_id = By.ID
        id_value = locator.get(by_id, None)
        if id_value is not None:
            if getattr(self, 'app_name') is not None:
                app_name = self.app_name if self.app_name is not None else ''
                if not id_value.startswith(f'{app_name}:id'):
                    id_value = f'{app_name}:id/' + id_value
            else:
                pass
            return by_id, id_value
        elif locator.get(xpath):
            language = self.language
            xpath_values = locator.get(xpath)
            if isinstance(xpath_values, list):
                if language == 'zh':
                    xpath_value = xpath_values[0]
                else:
                    xpath_value = xpath_values[1]
            else:
                xpath_value = xpath_values
            return xpath, xpath_value
        else:
            raise Exception("未找到定位方式")

    def find_element(self, locator):
        element = self._get_element(locator)
        return element

    def _get_element(self, locator):
        while True:
            try:
                value_text = locator.desc
            except:
                value_text = ""
            locator_name_value = self._get_locator_name_value(locator)
            if isinstance(locator_name_value, tuple):
                value = locator_name_value[1]
                if locator_name_value[0] == By.XPATH:
                    element = self.driver.find_element(By.XPATH, value)
                elif locator_name_value[0] == By.ID:
                    element = self.driver.find_element(By.ID, value)
                elif locator_name_value[0] == AppiumBy.ACCESSIBILITY_ID:
                    element = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, value)
                else:  # locator_t[0] == 'text':
                    element = self.driver.find_element(By.XPATH, f'//*[@text="{value}"]')
            else:
                element = self.driver.find_element(**locator_name_value)
                if element is None:
                    for k, v in locator_name_value.items():
                        element = self.driver.find_element(**{k: v})
                        if element is not None:
                            break
            if element is None:
                raise Exception(f"没有找到元素「{value_text}」")
            return element

    def click_element(self, locator):
        ele = self.find_element(locator)
        ele.click()

    def try_click_element(self, locator):
        try:
            ele = self.find_element(locator)
            ele.click()
        except Exception as e:
            logger.warning(f"点击元素失败: {e}")

    def click_locator(self, locator):
        _locator = getattr(self, locator, None) if isinstance(locator, str) else locator
        if _locator is None:
            if isinstance(locator, str) and locator in self.get_common_dialog():
                _locator = self.convert_to_xpath_locator(locator)
                self.click_element(_locator)
                goto_page = self.get_current_page_activity()
                return goto_page
        else:
            self.click_element(_locator)
            goto_page = getattr(_locator, 'goto_page', None)
            return goto_page

    def click_dialog(self, locator):
        locator_list = locator if isinstance(locator, list) else [locator]
        for name in locator_list:
            if isinstance(name, str):
                allPage = getattr(self, 'allPage')
                cur_page_locators = getattr(allPage, self.page_name)
                _locator = getattr(cur_page_locators, name)
            else:
                _locator = name
            if _locator:
                if self.ignore_dialog_error:
                    if isinstance(_locator, ElementProxy):
                        _locator.click()
                    elif isinstance(_locator, Locator):
                        self.try_click_element(_locator)
                else:
                    self.click_element(_locator)
            else:
                if isinstance(locator, str) and locator in self.get_common_dialog():
                    _locator = self.convert_to_xpath_locator(locator)
                    self.try_click_element(_locator)
                    goto_page = self.get_current_page_activity()
                    return goto_page
            time.sleep(1.5)

        current_page = self.get_current_page_activity()
        return current_page

    def get_current_page_activity(self):
        pattern = r"mCurrentFocus=Window\{[\w]+\s+u0\s+([\w\.\/]+)}"
        cmd = 'shell dumpsys window | findstr "mCurrentFocus"'
        activity_name = ''
        full_activity_path = ''
        for _ in range(3):
            ret = self.adb_operator.run_adb_cmd(cmd)
            match = re.search(pattern, ret, re.DOTALL)
            if match:
                full_activity_path = match.group(1)
                activity_name = full_activity_path.split('/')[-1].split('.')[-1]
            if activity_name:
                break
            time.sleep(1)
        else:
            ret_focus = self.adb_operator.run_adb_cmd("shell dumpsys activity top")
            focus_match = re.search(r'ACTIVITY\s+([^\s]+)', ret_focus)
            if focus_match:
                full_activity_path = focus_match.group(1)
                activity_name = full_activity_path.split('/')[-1].split('.')[-1]
        if activity_name:
            logger.info(f"当前页面: {activity_name}")
            logger.debug(f"当前页面的完整 Activity 路径: {full_activity_path}")
        else:
            logger.info(f"未获取到当前页面信息")
        return activity_name

    @staticmethod
    def dialog_activity():
        return ['GrantPermissionsActivity']

    def get_common_dialog(self):
        if self.language in ['zh', 'zh-CN']:
            dialog_names = ['确定', '同意', '允许', '取消']
        else:
            dialog_names = ['OK', 'Agree', 'Allow', 'Cancel']
        return dialog_names

    def handler_common_popup(self, by_type=By.XPATH, need_break=True):
        """
        通用弹框处理
        @return:
        """
        texts = self.get_common_dialog()
        _blank_list = [
            Locator(f'//*[@text="{texts[0]}"]', by_type=by_type, desc=f"{texts[0]}"),
            Locator(f'//*[@text="{texts[1]}"]', by_type=by_type, desc=f"{texts[1]}"),
            Locator(f'//*[@text="{texts[2]}"]', by_type=by_type, desc=f"{texts[2]}"),
            Locator(f'//*[@text="{texts[3]}"]', by_type=by_type, desc=f"{texts[3]}")
        ]
        logger.info("+++++++++++++通用弹框处理+++++++++++++")
        result_1, result_0 = [], []
        for index, locator in enumerate(_blank_list):
            try:
                self.click_element(locator)
                logger.info(f"找到了 {locator},并且已点击")
                result_1.append(locator)
                if need_break:
                    if index != len(_blank_list) - 1:
                        self.element_is_existed(_blank_list[index + 1])
                        continue
                    else:
                        break
            except Exception as e:
                e = str(e).replace('\n', '')
                logger.info(f"未找到『{locator.desc}』:{e}")
                result_0.append(locator)
        logger.info(f"共找到 {len(result_1)} 个弹框，分别为：{result_1}")
        logger.info(f"没有找到 {len(result_0)} 个弹框，分别为：{result_0}")

    @staticmethod
    def convert_to_xpath_locator(name):
        locator = Locator(f'//*[@text="{name}"]', by_type=By.XPATH)
        return locator


class ElementProxy:
    """
    元素代理对象，提供便捷的交互方法
    """

    def __init__(self, element_operator: ElementOperator, locator: Locator):
        self.operator = element_operator
        self.locator = locator

    def click(self, dialog=None, ignore_error=False):
        """
        点击元素，支持可选的弹框处理

        Args:
         dialog (可选): 处理弹框的可选参数
         ignore_error (bool): 是否忽略错误

        Raises:
            Exception: 如果发生错误且 ignore_error 为 False
        """
        if dialog:
            try:
                self.operator.click(self.locator, dialog=dialog)
            except Exception as e:
                if not ignore_error:
                    raise e
        else:
            self.operator.click(self.locator)

    def __str__(self):
        return str(self.locator)

    def __repr__(self):
        return repr(self.locator)
